﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Data;

namespace RoofusUIControls
{
    /// <summary>
    /// Draw a line from the center of one object to another. Deals correctly with the
    /// fact that the objects may be deeply embeded in some object that is embeded in some
    /// other object that is actually getting drawn upon.
    /// </summary>
    class ObjectCenterLine : DependencyObject
    {
        /// <summary>
        /// The actual line
        /// </summary>
        private Line _line = null;

        /// <summary>
        /// The canvas where the line is getting drawn.
        /// </summary>
        public Canvas Host
        {
            get { return (Canvas)GetValue(HostProperty); }
            set { SetValue(HostProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Host.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HostProperty =
            DependencyProperty.Register("Host", typeof(Canvas), typeof(ObjectCenterLine), new UIPropertyMetadata(null, new PropertyChangedCallback(ObjectConfigChanged)));

        /// <summary>
        /// The rectangle where the line should start from.
        /// </summary>
        public Rectangle LineStartObject
        {
            get { return (Rectangle)GetValue(LineStartObjectProperty); }
            set { SetValue(LineStartObjectProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineStartObject.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineStartObjectProperty =
            DependencyProperty.Register("LineStartObject", typeof(Rectangle), typeof(ObjectCenterLine), new UIPropertyMetadata(null, new PropertyChangedCallback(ObjectConfigChanged)));

        /// <summary>
        /// The rectangle where the line should finish up at.
        /// </summary>
        public Rectangle LineFinishObject
        {
            get { return (Rectangle)GetValue(LineFinishObjectProperty); }
            set { SetValue(LineFinishObjectProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineFinishObject.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineFinishObjectProperty =
            DependencyProperty.Register("LineFinishObject", typeof(Rectangle), typeof(ObjectCenterLine),
            new UIPropertyMetadata(null, new PropertyChangedCallback(ObjectConfigChanged)));



        public FrameworkElement LineSourceMoveMonitor
        {
            get { return (FrameworkElement)GetValue(LineSourceMoveMonitorProperty); }
            set { SetValue(LineSourceMoveMonitorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineSourceMoveMonitor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineSourceMoveMonitorProperty =
            DependencyProperty.Register("LineSourceMoveMonitor", typeof(FrameworkElement), typeof(ObjectCenterLine),
            new UIPropertyMetadata(null, new PropertyChangedCallback(ObjectConfigChanged)));



        public FrameworkElement LineSinkMoveMonitor
        {
            get { return (FrameworkElement)GetValue(LineSinkMoveMonitorProperty); }
            set { SetValue(LineSinkMoveMonitorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineSinkMonitor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineSinkMoveMonitorProperty =
            DependencyProperty.Register("LineSinkMoveMonitor", typeof(FrameworkElement), typeof(ObjectCenterLine),
            new UIPropertyMetadata(null, new PropertyChangedCallback(ObjectConfigChanged)));



        ///
        /// Simple guys to recalc the position on the fly.
        /// 
        private HalfWayCalculator _cl_x1_calc = new HalfWayCalculator();
        private HalfWayCalculator _cl_y1_calc = new HalfWayCalculator();
        private HalfWayCalculator _cl_x2_calc = new HalfWayCalculator();
        private HalfWayCalculator _cl_y2_calc = new HalfWayCalculator();


        /// <summary>
        /// Called when one of the properties that control how we draw the line has changed.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="args"></param>
        private static void ObjectConfigChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ///
            /// Check to see that everything we need is defined.
            /// 

            ObjectCenterLine cl = obj as ObjectCenterLine;
            if (cl == null)
            {
                return;
            }
            
            ///
            /// Is the current config valid?
            /// 

            bool is_configured = cl.Host != null
                && cl.LineFinishObject != null
                && cl.LineStartObject != null
                && cl.LineSourceMoveMonitor != null
                && cl.LineSinkMoveMonitor != null;

            ///
            /// If it isn't, but we already have a line, then we should null-i-fy it!
            /// 

            if (!is_configured && cl._line != null)
            {
                Canvas cv = cl.Host;
                if (args.Property == HostProperty)
                {
                    cv = args.OldValue as Canvas;
                }
                if (cv != null)
                {
                    cv.Children.Remove(cl._line);
                }
                cl._line = null;
            }

            ///
            /// Ok -- if we aren't configured, then there isn't much more we can do.
            /// 

            if (!is_configured) {
                return;
            }

            ///
            /// Get the line initalized
            /// 

            if (cl._line == null)
            {
                cl._line = new Line();
                Style mylinestyle = cl.Host.FindResource("SurfaceItemConnectorLineStyle") as Style;
                if (mylinestyle == null)
                {
                    cl._line.Stroke = Brushes.Black;
                }
                else
                {
                    cl._line.Style = mylinestyle;
                }
            }

            ///
            /// Confiure the calc'ers.
            /// 

            cl._cl_x1_calc.Host = cl.Host;
            cl._cl_y1_calc.Host = cl.Host;
            cl._cl_x2_calc.Host = cl.Host;
            cl._cl_y2_calc.Host = cl.Host;

            cl._cl_x1_calc.Object = cl.LineStartObject;
            cl._cl_y1_calc.Object = cl.LineStartObject;
            cl._cl_x2_calc.Object = cl.LineFinishObject;
            cl._cl_y2_calc.Object = cl.LineFinishObject;

            cl._cl_x1_calc.UseHorizontalCoordinate = true;
            cl._cl_y1_calc.UseHorizontalCoordinate = false;
            cl._cl_x2_calc.UseHorizontalCoordinate = true;
            cl._cl_y2_calc.UseHorizontalCoordinate = false;

            cl._cl_x1_calc.MoveMonitor = cl.LineSourceMoveMonitor;
            cl._cl_y1_calc.MoveMonitor = cl.LineSourceMoveMonitor;
            cl._cl_x2_calc.MoveMonitor = cl.LineSinkMoveMonitor;
            cl._cl_y2_calc.MoveMonitor = cl.LineSinkMoveMonitor;

            cl._cl_x1_calc.SetBinding(HalfWayCalculator.ObjectSizeProperty, CreateRBinding(cl.LineStartObject, Rectangle.ActualWidthProperty));
            cl._cl_y1_calc.SetBinding(HalfWayCalculator.ObjectSizeProperty, CreateRBinding(cl.LineStartObject, Rectangle.ActualHeightProperty));
            cl._cl_x2_calc.SetBinding(HalfWayCalculator.ObjectSizeProperty, CreateRBinding(cl.LineFinishObject, Rectangle.ActualWidthProperty));
            cl._cl_y2_calc.SetBinding(HalfWayCalculator.ObjectSizeProperty, CreateRBinding(cl.LineFinishObject, Rectangle.ActualHeightProperty));

            ///
            /// Create the appropriate bindings to the line itself.
            /// 

            cl._line.SetBinding(Line.X1Property, cl._cl_x1_calc.PositionBinding);
            cl._line.SetBinding(Line.Y1Property, cl._cl_y1_calc.PositionBinding);
            cl._line.SetBinding(Line.X2Property, cl._cl_x2_calc.PositionBinding);
            cl._line.SetBinding(Line.Y2Property, cl._cl_y2_calc.PositionBinding);

            ///
            /// Now, get the line added to the host.
            /// 

            cl.Host.Children.Add(cl._line);
        }

        /// <summary>
        /// Helper to create a binding
        /// </summary>
        /// <param name="rectangle"></param>
        /// <param name="dependencyProperty"></param>
        /// <returns></returns>
        private static Binding CreateRBinding(Rectangle rectangle, DependencyProperty dependencyProperty)
        {
            Binding result = new Binding();
            result.Source = rectangle;
            result.Path = new PropertyPath(dependencyProperty);
            return result;
        }

        /// <summary>
        /// Creates the line.
        /// </summary>
        /// <param name="lineHost"></param>
        public ObjectCenterLine()
        {
        }
    }
}
